﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

namespace TvdP
{
    public static partial class Adapters
    {
        internal static IEnumerable<Delegate> VerifiedThatRequiredSignatureIsMatched(this IEnumerable<Delegate> adapterComponents)
        { return adapterComponents.Select(Adapter.VerifiedThatRequiredSignatureIsMatched); }

        internal sealed class Composer : IAdapters, IResetable
        {
            public Composer(IEnumerable<Delegate> adapterComponentProvider)
            {
                AdapterComponentsRaw = adapterComponentProvider;

                var memoizedCompositeAdapterBuilder =
                    MemoizedWithReset(
                        (Type fromType) =>
                            MemoizedWithReset(
                                (Type toType) =>
                                    BuildCompositeAdapter(fromType, toType)
                            ).Item1
                    )
                ;

                GetCompositeAdapter = (fromType, toType) => memoizedCompositeAdapterBuilder.Item1(fromType)(toType);
                ResetGetCompositeAdapterCache = memoizedCompositeAdapterBuilder.Item2;

                var memoizedCacheBuilder =
                    MemoizedWithReset(
                        (Type fromType) => new WeakReference(null)
                    )
                ;

                GetAddaptationPathFinderCache = memoizedCacheBuilder.Item1;
                ResetGetAddaptationPathFinderCacheCache = memoizedCacheBuilder.Item2;

                Reset();
            }


            /// <summary>
            /// References from adaption results back to the adapted objects.
            /// </summary>
            internal ConditionalWeakTable<object, object> _BaseReferences = new ConditionalWeakTable<object, object>();

            internal static void CheckTypeParameter(Type type, string name)
            {
                if (type == null)
                    throw new ArgumentNullException(name);

                if (type.IsValueType)
                    throw new ArgumentException(name + " can not be a value type.", name);
            }

            /// <summary>
            /// Adapt the <paramref name="adaptee"/> to the target 'ToType'
            /// </summary>
            /// <param name="adaptee"></param>
            /// <returns></returns>
            object IAdapters.Cast(object adaptee, Type toType)
            {
                Composer.CheckTypeParameter(toType, "toType");

                if (adaptee == null)
                    return null;

                object baseAdaptee;

                if (_BaseReferences.TryGetValue(adaptee, out baseAdaptee))
                    adaptee = baseAdaptee;

                object solution = GetCompositeAdapter(adaptee.GetType(), toType)(adaptee);

                if (solution != null)
                    _BaseReferences.GetValue(solution, _ => adaptee);

                return solution;
            }

            /// <summary>
            /// Adapter that can not convert. Returns null and serves as a token that
            /// an adaptation can not be done.
            /// </summary>
            static Func<object, object> _NullAdapter = _ => null;

            Func<Type, Type, Func<object, object>> GetCompositeAdapter;
            Action ResetGetCompositeAdapterCache;

            Func<Type, WeakReference> GetAddaptationPathFinderCache;
            Action ResetGetAddaptationPathFinderCacheCache;

            IEnumerable<Delegate> AdapterComponentsRaw;
            IEnumerable<Delegate> AdapterComponents;

            CompositeAdapterBuilder CompositeAdapterBuilder = new CompositeAdapterBuilder();


            AdaptationPathFinder GetAddaptationPathFinder(Type fromType)
            {
                //AddaptationPathFinder objects are rather expensive to create.
                //Store them in a WeakReference (relative to the fromType) so they 
                //can potentialy be reused.
                //
                //When the composer is reset this cache needs to be cleared.
                var wr = GetAddaptationPathFinderCache(fromType);

                var AddaptationPathFinder = (AdaptationPathFinder)wr.Target;

                if (AddaptationPathFinder == null)
                    wr.Target = AddaptationPathFinder = new AdaptationPathFinder(fromType, AdapterComponents);

                return AddaptationPathFinder;
            }

            Func<object, object> BuildCompositeAdapter(Type fromType, Type toType)
            {
                var AddaptationPathFinder = GetAddaptationPathFinder(fromType);

                var path = AddaptationPathFinder.FindPathTo(toType);

                if (path == null)
                    return _NullAdapter;

                return CompositeAdapterBuilder.BuildAdapter(path);
            }

            object IAdapters.GetBase(object solution)
            {
                if (solution == null)
                    return null;

                object b;

                return _BaseReferences.TryGetValue(solution, out b) ? b : solution;
            }

            bool IAdapters.CanCast(Type fromType, Type toType)
            {
                Composer.CheckTypeParameter(fromType, "fromType");
                Composer.CheckTypeParameter(toType, "toType");

                return !object.ReferenceEquals(GetCompositeAdapter(fromType, toType), _NullAdapter);
            }

            /// <summary>
            /// Resets relevant caches. Does not clear adaptee -> solution and solution -> adaptee relations.
            /// </summary>
            public void Reset()
            {
                AdapterComponents =
                    AdapterComponentsRaw.VerifiedThatRequiredSignatureIsMatched().ToList();

                this.ResetGetAddaptationPathFinderCacheCache();
                this.ResetGetCompositeAdapterCache();
            }
        }

        /// <summary>
        /// Create and IAdapters using the given IAdapterComponentProvider as input.
        /// </summary>
        /// <remarks>
        /// The resulting IAdaterStore will also implement IResetable. The Reset() method should be called whenever the content of <paramref name="adapterComponentCollection"/> changes. 
        /// </remarks>
        /// <param name="adapterComponentCollection">The collection that provides the adapter delegates for the store</param>
        /// <returns>The newly created IAdapters object</returns>
        public static IAdapters CreateComposed(IEnumerable<Delegate> adapterComponentCollection)
        {
            if (adapterComponentCollection == null)
                throw new ArgumentNullException("adapterComponentCollection");

            return new Composer(adapterComponentCollection);
        }

    }
}
